package com.yuyou.fn.controller;

import com.alibaba.fastjson.JSONObject;
import com.github.wens.mybatisplus.toolkit.IdWorker;
import com.google.common.hash.Hashing;
import com.yuyou.fn.common.exception.BusException;
import com.yuyou.fn.common.service.IBusFailLogService;
import com.yuyou.fn.common.service.ISiteConfigService;
import com.yuyou.fn.common.util.Principals;
import com.yuyou.fn.common.util.TimeUtils;
import com.yuyou.fn.educational.entity.CourseOrder;
import com.yuyou.fn.educational.service.ICourseOrderService;
import com.yuyou.fn.educational.vo.ConfirmPayInfo;
import com.yuyou.fn.educational.vo.PayInfoOfCourseOrder;
import com.yuyou.fn.educational.vo.PayLecture;
import com.yuyou.fn.settlement.entity.AccountRecharge;
import com.yuyou.fn.settlement.entity.PosPayRecord;
import com.yuyou.fn.settlement.service.IAccountRechargeService;
import com.yuyou.fn.settlement.service.IBalanceLockRecordService;
import com.yuyou.fn.settlement.service.IMainAccountService;
import com.yuyou.fn.settlement.service.IPosPayService;
import com.yuyou.fn.settlement.vo.PayWay;
import com.yuyou.fn.vo.AllinPayBaseResponseVo;
import com.yuyou.fn.vo.AllinPayOrderResponseVo;
import io.swagger.annotations.ApiOperation;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 对接pos支付
 */
@Controller
@RequestMapping(value = { "/pos/api" ,"/allinpay/api" })
class PosPayApiController {

    private final static Logger log  = LoggerFactory.getLogger(PosPayApiController.class);


    private String appId;
    private String appKey;

    @Resource
    private IPosPayService posPayService ;

    @Resource
    private ISiteConfigService siteConfigService;

    @Resource
    private ICourseOrderService courseOrderService;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private IBusFailLogService busFailLogService;

    @Resource
    private IAccountRechargeService accountRechargeService;

    @Resource
    private IBalanceLockRecordService balanceLockRecordService ;

    @PostConstruct
    public void init() {

        appId = siteConfigService.getConfigValueFromCache("ALLINPAY_APPID");
        appKey = siteConfigService.getConfigValueFromCache("ALLINPAY_APPKEY");
    }


    @ApiOperation("查询订单")
    @RequestMapping(value = "/order/query", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public String order(HttpServletRequest request) {

        TreeMap<String, String> params = getParams(request);
        log.info("allin query:" + JSONObject.toJSONString(params) );


        try {
            if (!validSign(params)) {
                return JSONObject.toJSONString(getDefaultMsg("验签失败"));
            }

            String requestPayId = params.get("bizseq");

            if(posPayService.isSuccessPay(Long.valueOf(requestPayId))){
                return JSONObject.toJSONString(getDefaultMsg("不要重复支付"));
            }

            PosPayRecord payRecord = posPayService.findPayRecord(Long.valueOf(requestPayId));



            HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();

            String cusId  = hashOperations.get("campus:cusId:config" , String.valueOf( payRecord.getChargeCampusId() ) );
            if( cusId == null ){
                return JSONObject.toJSONString(getDefaultMsg("收费校区未设置商户号"));
            }

            if( !params.get("cusid").equalsIgnoreCase(cusId)){
                log.debug("商户号不一致,query cusid={},sys cusid={}",params.get("cusid"),cusId);
                return JSONObject.toJSONString(getDefaultMsg("商户号不一致"));
            }

            AllinPayOrderResponseVo allinPayOrderResponseVo = new AllinPayOrderResponseVo();
            allinPayOrderResponseVo.setBizseq(requestPayId);
            allinPayOrderResponseVo.setAmount(payRecord.getAmount().longValue());
            allinPayOrderResponseVo.initHead(cusId, appId);

            if(payRecord.getBusType() == 0 ){
                checkStatusForCourseOrder(payRecord, allinPayOrderResponseVo);
            }else if(payRecord.getBusType() ==1 ){
                checkoutStatusForRecharge( payRecord , allinPayOrderResponseVo);
            }else{
                return JSONObject.toJSONString(getDefaultMsg("无法获取订单信息"));
            }

            if( payRecord.getStatus() != 0
                    || TimeUtils.now().getTime() - payRecord.getCreateTime().getTime() > TimeUnit.MINUTES.toMillis(5 )
                    || !posPayService.updateStatus( Long.valueOf(requestPayId) ,PosPayRecord.STATUS.初始,PosPayRecord.STATUS.已扫码 )){
                return JSONObject.toJSONString(getDefaultMsg("此码已经过期")) ;
            }

            JSONObject extra = JSONObject.parseObject(StringUtils.defaultString(payRecord.getExtra(), "{}"));

            if( extra.containsKey("otherPays")){
                List<ConfirmPayInfo.Pay> otherPays = extra.getJSONArray("otherPays").toJavaList(ConfirmPayInfo.Pay.class);
                try{
                    otherPays.stream().filter(p -> p.getPayWay() == 3 ).forEach(p -> {
                        balanceLockRecordService.lock(extra.getLong("mainAccountId" ) , DigestUtils.md5Hex(requestPayId + "pos") , p.getAmount() ,"pos机锁定余额") ;
                    });
                }catch (BusException e){
                    return JSONObject.toJSONString(getDefaultMsg("锁定学生账户余额失败"));
                }

            }

            addSign(allinPayOrderResponseVo);
            return JSONObject.toJSONString(allinPayOrderResponseVo);
        } catch (Exception e) {//处理异常
            log.error("query order fail" , e );
            return JSONObject.toJSONString(getDefaultMsg("处理异常"));
        }
    }

    private void checkoutStatusForRecharge(PosPayRecord posPayRecord, AllinPayOrderResponseVo allinPayOrderResponseVo) {
        AccountRecharge accountRecharge = accountRechargeService.findById(posPayRecord.getBusOrderId());
        allinPayOrderResponseVo.setTrxreserve("05#recharge##########");
        if (accountRecharge.getState() == 0 ) {
            allinPayOrderResponseVo.setRetcode("0000");
        } else {
            allinPayOrderResponseVo.setRetcode("9999");
            allinPayOrderResponseVo.setRetmsg("订单支付已支付或已撤销");
        }
    }

    private void checkStatusForCourseOrder(PosPayRecord payRecord, AllinPayOrderResponseVo allinPayOrderResponseVo) {
        CourseOrder courseOrder = courseOrderService.findById(payRecord.getBusOrderId());
        allinPayOrderResponseVo.setTrxreserve("05#buyCourse##########");
        if (courseOrder.getChargingStatus() == 0 || courseOrder.getChargingStatus() == 1) {
            allinPayOrderResponseVo.setRetcode("0000");
        } else {
            allinPayOrderResponseVo.setRetcode("9999");
            allinPayOrderResponseVo.setRetmsg("订单支付已支付或已撤销");
        }
    }


    @ApiOperation("支付回调")
    @RequestMapping(value = "/notify/callback", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public String  notifyCallback(HttpServletRequest request) {

        TreeMap<String, String> params = getParams(request);
        log.info("allin notify:" + JSONObject.toJSONString(params) );
        try {
            if (!validSign(params)) {
                return "error";
            }

            String trxstatus  = params.get("trxstatus");

            if( !"0000".equals( trxstatus ) ){
                return "error" ;
            }

            String requestPayId  = params.get("bizseq");


            String trxId  = params.get("trxid");
            String amount  = params.get("amount");
            String termId  = params.get("termid");
            String trxCode  = params.get("trxcode");
            String termBatchId  = params.get("termbatchid");
            String traceNo  = params.get("traceno");
            String cusId  = params.get("cusid");

            JSONObject payInfoExt = new JSONObject() ;
            payInfoExt.put("termId" ,termId );//终端编码
            payInfoExt.put("trxCode" ,trxCode );//交易类型
            payInfoExt.put("termBatchId" ,termBatchId );//终端批次号
            payInfoExt.put("traceNo" ,traceNo );//终端流水
            payInfoExt.put("cusId" ,cusId );//商户号
            payInfoExt.put("bizseq" ,requestPayId );//支付流水订单号
            payInfoExt.put("trxId" ,trxId );//交易流水号

            posPayService.successPay(Long.valueOf(requestPayId),payInfoExt , callbackFunction());

            return "success" ;

        } catch (Exception e) {//处理异常
            log.error("notifyCallback fail" , e );
            busFailLogService.log("allinPayNotify" , JSONObject.toJSONString(params));
            return "error";
        }
    }

    private Function<PosPayRecord, Boolean> callbackFunction() {
        return (posPayRecord)->{
            if(posPayRecord.getBusType() ==  0 ){
                JSONObject extra = JSONObject.parseObject(posPayRecord.getExtra());
                CourseOrder courseOrder = courseOrderService.findById(posPayRecord.getBusOrderId());

                PayInfoOfCourseOrder payInfoOfCourseOrder = new PayInfoOfCourseOrder();
                payInfoOfCourseOrder.setPayAmount(posPayRecord.getAmount().divide(BigDecimal.valueOf(100)));
                payInfoOfCourseOrder.setPayLectures(extra.getJSONArray("payLectures").toJavaList(PayLecture.class));
                payInfoOfCourseOrder.setPrincipal(extra.getJSONObject("operator").toJavaObject(Principals.Principal.class));
                payInfoOfCourseOrder.setPayWays(Arrays.asList(new PayWay(1,payInfoOfCourseOrder.getPayAmount(),posPayRecord.getPosInfo())));
                payInfoOfCourseOrder.setChargeCampusId(posPayRecord.getChargeCampusId());
                if(extra.containsKey("otherPays")){
                    List<ConfirmPayInfo.Pay> otherPays = extra.getJSONArray("otherPays").toJavaList(ConfirmPayInfo.Pay.class);
                    otherPays.stream().filter(p -> p.getPayWay() == 3 ).forEach(p -> {
                        balanceLockRecordService.unlock(extra.getLong("mainAccountId" ) , DigestUtils.md5Hex(posPayRecord.getRequestPayId() + "pos") , p.getAmount() );
                    });

                    List<PayWay> payWays = new ArrayList<>(otherPays.size() + 1);
                    payWays.addAll(otherPays.stream().map( p -> new PayWay(p.getPayWay(),p.getAmount(),null)).collect(Collectors.toList()));
                    payWays.addAll(payInfoOfCourseOrder.getPayWays());
                    payInfoOfCourseOrder.setPayWays(payWays);
                    payInfoOfCourseOrder.setPayAmount(payWays.stream().map(PayWay::getAmount).reduce(BigDecimal.ZERO,BigDecimal::add));
                }
                courseOrderService.payCourseOrder(courseOrder,payInfoOfCourseOrder);
            }else if(posPayRecord.getBusType() ==  1 ){
                JSONObject extra = JSONObject.parseObject(posPayRecord.getExtra());
                accountRechargeService.finishAccountRecharge(posPayRecord.getBusOrderId() ,1 ,posPayRecord.getPosInfo(),extra.getJSONObject("operator").toJavaObject(Principals.Principal.class),posPayRecord.getAmount().divide(BigDecimal.valueOf(100)));
            }
            return Boolean.TRUE ;

        };
    }

    @ApiOperation("交易结果查询查询")
    @RequestMapping(value = "/query/result", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public  String queryResult(@RequestParam("cusid") String cusid , @RequestParam("trxid") String trxid ) throws IOException {
        OkHttpClient client = new OkHttpClient.Builder().retryOnConnectionFailure(true).connectTimeout(30, TimeUnit.SECONDS ).readTimeout(10,TimeUnit.SECONDS).build();
        String url  = "http://lakala.darga.cn/api/TransactionQuery/GetRecord" ;
        //String url  = "https://vsp.allinpay.com/apiweb/tranx/queryorder" ;
        Map<String,String> params = new HashMap<>();
        params.put("orgid" ,"470581082991756");
        params.put("cusid" ,cusid );
        params.put("appid" ,appId );
        params.put("trxid" ,trxid );
        params.put("randomstr" ,String.valueOf(IdWorker.getId()));
        params.put("resendnotify" ,"1");

        FormBody.Builder formBody = new FormBody.Builder();
        for(Map.Entry<String,String> entry : params.entrySet() ){
            formBody.add(entry.getKey(),entry.getValue());
        }
        formBody.add("sign", addSign(params));

        Request request = new Request.Builder().url(url).post(formBody.build()).build() ;

        Response response = client.newCall(request).execute();
        return response.body().string() ;

    }

    @ApiOperation("手动回调")
    @RequestMapping(value = "/manual/notify/callback", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public  String manualCallback(@RequestParam("requestPayId") Long requestPayId , @RequestParam("sign") String sign ) throws IOException {

        if(!DigestUtils.md5Hex(requestPayId + appKey).equalsIgnoreCase(sign)){
            return "FAIL";
        }
        posPayService.successPay(Long.valueOf(requestPayId),null, callbackFunction());
        return "OK" ;

    }


    @ApiOperation("支付元信息")
    @RequestMapping(value = "/query/payInfo", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public  PosPayRecord payInfo(@RequestParam("requestPayId") Long  requestPayId ) throws IOException {
        PosPayRecord posPayRecord = posPayService.findPayRecord(requestPayId);
        return posPayRecord ;
    }


    private  String  addSign(Map<String,String> params ){
        try{
            return sign(params);
        }catch (Exception e ){
        }
        return StringUtils.EMPTY;

    }

    private String sign(Map<String, String> params) throws UnsupportedEncodingException {
        Map<String,String> map = new TreeMap<String,String>(params);
        StringBuilder sb = new StringBuilder();
        map.put("key", appKey );
        for(Map.Entry<String, String> entry:map.entrySet()){
            if(StringUtils.isNotEmpty( entry.getValue())){
                sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            }
        }
        if(sb.length()>0){
            sb.deleteCharAt(sb.length()-1);
        }
        String blank = sb.toString();
        return Hashing.md5().hashBytes(blank.getBytes("utf-8")).toString();
    }


    private TreeMap<String, String> getParams(HttpServletRequest request) {
        TreeMap<String, String> map = new TreeMap<String, String>();
        Map reqMap = request.getParameterMap();
        for (Object key : reqMap.keySet()) {
            map.put(key.toString(), ((String[]) reqMap.get(key))[0]);
        }
        return map;
    }

    private boolean validSign(TreeMap<String, String> param) throws Exception {
        if (param != null && !param.isEmpty()) {
            if (!param.containsKey("sign")) {
                return false;
            }
            param.put("key", appKey);//将分配的appkey加入排序
            StringBuilder sb = new StringBuilder();
            String sign = param.get("sign").toString();
            param.remove("sign");
            for (String key : param.keySet()) {
                String value = param.get(key);
                if (!StringUtils.isEmpty(value))
                    sb.append(key).append("=").append(value).append("&");
            }
            if (sb.length() > 0) {
                sb.deleteCharAt(sb.length() - 1);
            }
            String blank = sb.toString();
            return sign.toLowerCase().equals(Hashing.md5().hashBytes(blank.getBytes("utf-8")).toString());
        }

        return false;
    }


    public AllinPayBaseResponseVo getDefaultMsg(String msg) {
        AllinPayBaseResponseVo rsp = new AllinPayBaseResponseVo();
        rsp.initHead("888", appId);
        rsp.setRetcode("9999");
        rsp.setRetmsg(msg);
        addSign(rsp);
        return rsp;
    }

    public void addSign(AllinPayBaseResponseVo vo){
        try{
            JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(vo));
            Map<String,String> map = new HashMap<>();
            for(String key : jsonObject.keySet()){
                map.put(key,jsonObject.getString(key));
            }
            String sign = sign(map);
            vo.setSign(sign.toLowerCase());
        }catch (Exception e ){
            log.error("addSign fail" , e );
        }

    }


}
